package org.flashbrighton.as3bots
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	
	import org.flashbrighton.as3bots.collision.Collidable;
	import org.flashbrighton.as3bots.collision.CollidableType;
	import org.flashbrighton.as3bots.collision.CollisionDetector;
	import org.flashbrighton.as3bots.collision.SweepTestResults;
	import org.flashbrighton.as3bots.display.HitPoint;
	import org.flashbrighton.as3bots.events.BotEvent;
	import org.flashbrighton.as3bots.geom.Metrics;
	import org.flashbrighton.as3bots.logic.*;
	import org.flashbrighton.as3bots.utils.ArrayUtils;

	
	/**
	 * This is the main class for the game. As well as
	 * refereeing the game, it controls the Bots' 
	 * movements and detects all collisions.
	 */
	public class Game extends Sprite
	{
		private var _arena:Arena;
		private var _bots:Array;
		private var _scoreBoard:TextField;
		private var _isPlaying:Boolean;
		private var _collisionDetector:CollisionDetector;
		
		private const MAX_HIT_POINT:int = 100;

		private var _trails:Sprite;
		
		/**
		 * Constructor
		 */
		public function Game()
		{
			init();
		}
		
		
		/**
		 * Sets up the bots, the arena
		 * and starts the main loop.
		 */
		private function init():void
		{
			_collisionDetector = new CollisionDetector();
			_trails = new Sprite();
			
			_bots = new Array();
			
			registerBot(new NeilsBotLogic(), "Neil", 0xFF00FF);
			registerBot(new JoesBotLogic(), "Joe", 0xD00D1E);
			registerBot(new StusBotLogic(), "Stu", 0xFFF886);
			registerBot(new ArmageddonBotLogic(), "LukeOwen", 0xDEFEA7);
			registerBot(new RichsBotLogic(), "Rich", 0xACCEDE);
			registerBot(new cjbotlogic(), "CJ", 0x5A7ADE);
			registerBot(new SebsBotLogic(), "Seb", 0xC0FFEE);
			registerBot(new OlliesBotLogic(), "Ollie", 0xDABB1E);
			
			createArena();
			createBots();
			createScoreBoard();
			
			addEventListener(Event.ENTER_FRAME, mainLoop);
			
			//_isPlaying = true;			
			addEventListener(MouseEvent.CLICK, resume); //testing
		}
		
		private function resume(e:Event=null):void
		{
			_isPlaying = true;
		}
		
		/**
		 * Creates the Arena
		 * NOTE: this must be the first item added to the 
		 * CollisionDetector
		 */
		private function createArena():void
		{
			_arena = new Arena(new Metrics(385, 264, 250));
			_collisionDetector.registerCollidable(_arena)
			addChild(_arena);
		}
		
		/**
		 * Creates the Score Board
		 */
		private function createScoreBoard():void
		{
			_scoreBoard = new TextField();
			_scoreBoard.autoSize = TextFieldAutoSize.LEFT;
			addChild(_scoreBoard);
		}
		
		
		private function registerBot(logic:IBotLogic, name:String, colour:uint=0x00FF00):void
		{
			var bot:Bot;
			bot = new Bot(new Metrics(), logic, colour);
			bot.name = name;
			_bots.push(bot);
		}
		
		/**
		 * Adds a specified number of bots to the Stage.
		 * 
		 * @param numBots The number of bots to add to the Stage.
		 */
		private function createBots():void
		{
			var n:int = 0,
				br:Number,
				bx:Number,
				by:Number,
				brad:Number,
				bot:Bot;
			
			var numBots:int = _bots.length;
			
			_bots = ArrayUtils.shuffle(_bots);
			
			for each (bot in _bots)
			{
				br = (360 /numBots * n);				
				brad = br*Math.PI/180;
				bx = _arena.x + Math.cos(brad)*(_arena.metrics.circle.radius*0.75);
				by = _arena.y + Math.sin(brad)*(_arena.metrics.circle.radius*0.75);
				
				bot.metrics.circle.x = bx;
				bot.metrics.circle.y = by;
				bot.metrics.circle.rotation = br - 180;
				bot.applyPos(0);
				bot.applyRotation();
				
				_collisionDetector.registerCollidable(bot);
				addChild(bot);
				
				n++;
			}
			
		}

		
		/**
		 * The main loop of the game. The methods here 
		 * are executed on every frame.
		 */
		private function mainLoop(event:Event):void
		{
			
			if (!_isPlaying)
			{
				updateScoreBoard();
				return;
			}
			moveBots();
			detectCollisions();
			checkScores();
			updateScoreBoard();
		}
		
		
		/**
		 * Moves the bots
		 */
 		private function moveBots():void
 		{
 			var n:int = 0;
			var bot:Bot;
			
			while (n < _bots.length)
			{
				bot = Bot(_bots[n]);
				
				_trails.graphics.lineStyle(2, bot._colour, 0.5);
				_trails.graphics.moveTo(bot.x, bot.y);
			
				bot.mainLoop();	
				bot.rotate();	
				bot.move();
				
				_trails.graphics.lineTo(bot.x+1, bot.y);
				
				n++;
			}
 		}
 		
 		
		/**
		 * Detects all collisions between each sprite
		 * in the game.
		 */
 		private function detectCollisions():void
 		{
 			var n:int = 0;
			var	bot:Bot;
			var	results:SweepTestResults;
			
			// returns the results of the predictive test	
			results = _collisionDetector.getFirstCollisionTime();

			for each (bot in _bots)
			{				
				bot.applyRotation();
				// apply the results to each bot
				applyCollisionResults(bot, results);
			}
			
			// send notifications to colliding bots
 			notifyCollidables(results);
 		}
 		
 		
 		/**
		 * Applys the new positions to the bots
		 * depending on the results.
		 */
 		private function applyCollisionResults(bot:Collidable, results:SweepTestResults):void
 		{
 			switch (true)
			{
				// no collisions
				case (results.time == -1):
					// apply positions at time 1 (ie the full vector)
					bot.applyPos(1);
					break; 
				
				// bot has collided with arena wall
				case (results.numCollisions == 1 && (_arena == results.collidableA || _arena == results.collidableB)):
					// roll back to time of collision
					bot.applyPos(results.time);
					break;
				
				// one collision, the bot is colliding
				case (results.numCollisions == 1 && (bot == results.collidableA || bot == results.collidableB)):
					// roll back to time of collision
					bot.applyPos(results.time);
					break;
					
				// one collision, the bot is not colliding
				case (results.numCollisions == 1):
					// apply positions at time 1 (ie the full vector)	
					bot.applyPos(1);
					break;
				
				// multiple collisions
				case (results.numCollisions > 1):
					// roll back to time of collision
					bot.applyPos(results.time);
					break;
			}
 		}
		 
		 
		/**
		 * If there is a collision, notify the colliding items.
		 */
		private function notifyCollidables(results:SweepTestResults):void
		{
			var botA:Bot;
			var botB:Bot;
			var pointA:Number;
			var pointB:Number;
			var healthA:Number;
			var healthB:Number;
			
			if (results.numCollisions == 0) return;
			
			if (results.collidableA == _arena)
			{
				// Collision with arena wall
				pointA = Bot(results.collidableB).getHitPoint(_arena);
				if (pointA < 0) pointA = -pointA;
				
				healthA = Math.round((1 - pointA) * -MAX_HIT_POINT);
				
				notifyCollision(results.collidableB, 1 - pointA, healthA, CollidableType.WALL);
				showHitPoint(healthA, results.collidableB);
			}
			else
			{
				// Collision between bots
				botA = Bot(results.collidableA);
				botB = Bot(results.collidableB);
				
				pointA = botA.getHitPoint(botB);
				pointB = botB.getHitPoint(botA);
				if (pointA < 0) pointA = -pointA;
				if (pointB < 0) pointB = -pointB;
				
				//trace(pointA, pointB); 
				
				var pointsA:Number = pointA-pointB; 
				var pointsB:Number = pointB-pointA; 
				
				healthA = Math.round(pointsA * -MAX_HIT_POINT);
				healthB = Math.round(pointsB * -MAX_HIT_POINT);
								
				notifyCollision(botA, pointA, healthA, CollidableType.BOT);
				notifyCollision(botB, pointB, healthB, CollidableType.BOT);
				
				showHitPoint(healthA, botA);
				showHitPoint(healthB, botB);
			}
		}
		
		
		private function notifyCollision(bot:Collidable, collisionPoint:Number, points:int, targetType:String):void
		{
			bot.notify(new BotEvent(BotEvent.HIT, collisionPoint, points, targetType));
		}
		
		
		private function showHitPoint(amount:Number, bot:Collidable):void
		{
			var hp:HitPoint = new HitPoint(amount);
			hp.x = bot.x;
			hp.y = bot.y;
			addChild(hp);
		}
		
		
		private function checkScores():void
		{			
			for each (var bot:Bot in _bots)
			{
				if (bot.energy < 1)
				{
					_collisionDetector.removeCollidable(bot);
					_bots.splice(_bots.indexOf(bot), 1);
					removeChild(bot);
				}
				
				if (_bots.length == 1)
				{
					_isPlaying = false;
					addChild(_trails);
					break;
				}
			}
		}
		
		
		private function updateScoreBoard():void
		{
			var scores:String = "";
			var colour:String;
			
			for each (var bot:Bot in _bots)
			{
				colour = bot._colour.toString(16);
				scores += "<font color='#" + colour +"' face='Arial'>" + bot.name + " : " + bot.energy + "</font>\n";
			}
			
			_scoreBoard.htmlText = scores;
		}
		
	}
}